package com.cauli.manage.menu.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cauli.manage.menu.mapper.MenuMapper;
import com.cauli.manage.menu.model.dto.MenuAddDTO;
import com.cauli.manage.menu.model.vo.MenuVO;
import com.cauli.manage.menu.model.dto.MenuQueryDTO;
import com.cauli.manage.menu.model.dto.MenuUpdateDTO;
import com.cauli.manage.menu.model.dto.MenuUpdateStatusDTO;
import com.cauli.manage.menu.model.dto.MetaDTO;
import com.cauli.manage.menu.model.dto.RouterDTO;
import com.cauli.manage.menu.model.dto.TreeSelectedDTO;
import com.cauli.manage.menu.model.entity.Menu;
import com.cauli.manage.menu.model.enums.MenuComponentEnum;
import com.cauli.manage.menu.model.enums.MenuTypeEnum;
import com.cauli.manage.menu.service.MenuService;
import com.cauli.manage.role.mapper.RoleMapper;
import com.cauli.manage.role.model.entity.Role;
import com.cauli.manage.user.mapper.UserMapper;
import com.cauli.manage.user.model.entity.User;
import com.cauli.utils.exception.MyException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author Cauli
 * @date 2022-12-13 15:15:47
 * @description 菜单 服务实现类
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Override
    public List<Menu> getMenuList(MenuQueryDTO menuQueryDTO) {
        return baseMapper.selectList(menuQueryDTO.toLambdaQueryWrapper());
    }

    @Override
    public MenuVO getMenuById(Long menuId) {
        Menu menu = baseMapper.selectById(menuId);
        MenuVO menuVO = new MenuVO();
        if (menu != null) {
            BeanUtils.copyProperties(menu, menuVO);
        }
        return menuVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addMenu(MenuAddDTO menuAddDTO) {
        Menu menu = new Menu();
        BeanUtil.copyProperties(menuAddDTO, menu);
        if (menu.getMenuName() == null) {
            throw new MyException("菜单名为空");
        }
        // 检测菜单名是否被占用
        this.checkIsDuplicated(menuAddDTO.getMenuName());
        baseMapper.insert(menu);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateMenu(MenuUpdateDTO menuUpdateDTO) {
        // 根据菜单ID获取菜单信息
        Menu menu = baseMapper.selectById(menuUpdateDTO.getMenuId());
        BeanUtil.copyProperties(menuUpdateDTO, menu);
        if (menu == null) {
            throw new MyException("menu不存在");
        }
        // 检测菜单名是否重复
        if (StrUtil.isEmpty(menuUpdateDTO.getMenuName()) && !menu.getMenuName().equals(menuUpdateDTO.getMenuName())) {
            this.checkIsDuplicated(menuUpdateDTO.getMenuName());
        }
        baseMapper.updateById(menu);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changeMenuStatus(MenuUpdateStatusDTO menuUpdateStatusDTO) {
        // 根据菜单ID获取菜单信息
        Menu menu = baseMapper.selectById(menuUpdateStatusDTO.getMenuId());
        if (menu == null) {
            throw new MyException("菜单不存在");
        }
        BeanUtil.copyProperties(menuUpdateStatusDTO, menu);
        baseMapper.updateById(menu);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteMenu(List<Long> menuIds) {
        for (Long menuId : menuIds) {
            // 根据菜单ID获取菜单信息
            Menu menu = baseMapper.selectById(menuId);
            baseMapper.deleteById(menu);
        }
    }

    @Override
    public List<MenuVO> getPermissionList(Long userId, Integer menuType) {
        if (userId == null) {
            return null;
        }
        return menuMapper.getPermissionList(userId, menuType);
    }

    @Override
    public List<Tree<Long>> getDropdownList(Long userId) {
        // 根据用户ID查询系统菜单列表
        List<Menu> menuList = menuMapper.getMenuListByUserId(userId);
        // 构建前端所需要树结构
        return buildMenuTreeSelect(menuList);
    }

    @Override
    public TreeSelectedDTO getRoleDropdownList(Long userId, Long roleId) {
        List<Menu> menus = null;

        // 判断该用户是否为超级管理员的角色
        User user = userMapper.selectById(userId);
        LambdaQueryWrapper<Role> roleLambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (user.getRoleId() != null) {
            roleLambdaQueryWrapper.eq(Role::getRoleId, user.getRoleId());
            Role role = roleMapper.selectOne(roleLambdaQueryWrapper);
            menus = Objects.equals(role.getRoleKey(), "admin") ? this.list() : menuMapper.getMenuListByUserId(userId);
        }

        List<Tree<Long>> trees = buildMenuTreeSelect(menus);
        List<Long> menuIds = baseMapper.getMenuIdListByRoleId(roleId);

        TreeSelectedDTO tree = new TreeSelectedDTO();
        tree.setMenus(trees);
        tree.setCheckedKeys(menuIds);
        return tree;
    }

    @Override
    public List<RouterDTO> getRouters(Long userId) {
        // 构建路由树结构
        List<Tree<Long>> trees = buildMenuEntityTree(userId);
        // 将树结构转化为列表结构
        return buildRouterTree(trees);
    }

    /**
     * 检测菜单名是否被占用
     *
     * @param name
     */
    private void checkIsDuplicated(String name) {
        if (baseMapper.exists(new LambdaQueryWrapper<Menu>().eq(Menu::getMenuName, name))) {
            throw new MyException("该menu名已被占用");
        }
    }

    /**
     * 构建前端所需要树结构
     *
     * @param menuList 菜单列表
     * @return 树结构列表
     */
    public List<Tree<Long>> buildMenuTreeSelect(List<Menu> menuList) {
        TreeNodeConfig config = new TreeNodeConfig();
        // 默认为id可以不设置，这里为menuId
        config.setIdKey("menuId");
        return TreeUtil.build(menuList, 0L, config, (menu, tree) -> {
            // 也可以使用 tree.setId(dept.getId()); 等一些默认值
            tree.setId(menu.getMenuId());
            tree.setParentId(menu.getParentId());
            tree.putExtra("label", menu.getMenuName());
        });
    }

    /**
     * 构建路由树结构
     *
     * @param userId
     * @return
     */
    public List<Tree<Long>> buildMenuEntityTree(Long userId) {
        List<Menu> allMenus = null;

        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Menu::getStatus, true);

        // 判断该用户是否为超级管理员的角色
        User user = userMapper.selectById(userId);
        if (user.getRoleId() != null) {
            LambdaQueryWrapper<Role> roleLambdaQueryWrapper = new LambdaQueryWrapper<>();
            roleLambdaQueryWrapper.eq(Role::getRoleId, user.getRoleId());
            Role role = roleMapper.selectOne(roleLambdaQueryWrapper);
            if ("admin".equals(role.getRoleKey())) {
                allMenus = menuMapper.selectList(queryWrapper);
            } else {
                allMenus = menuMapper.getMenuListByUserId(userId);
            }
        }

        // 去除属于按钮的路由
        List<Menu> noButtonMenus = null;
        if (allMenus != null) {
            noButtonMenus = allMenus.stream()
                    .filter(menu -> !MenuTypeEnum.BUTTON.getValue().equals(menu.getMenuType()))
                    .collect(Collectors.toList());
        }

        TreeNodeConfig config = new TreeNodeConfig();
        //默认为id可以不设置
        config.setIdKey("menuId");
        return TreeUtil.build(noButtonMenus, 0L, config, (menu, tree) -> {
            // 也可以使用 tree.setId(dept.getId());等一些默认值
            tree.setId(menu.getMenuId());
            tree.setParentId(menu.getParentId());
            tree.putExtra("entity", menu);
        });
    }

    /**
     * 将树结构转化为列表结构
     *
     * @param trees
     * @return
     */
    public List<RouterDTO> buildRouterTree(List<Tree<Long>> trees) {
        List<RouterDTO> routerList = new LinkedList<>();
        if (CollUtil.isNotEmpty(trees)) {
            for (Tree<Long> tree : trees) {
                RouterDTO routerDTO;

                Menu menu = (Menu) tree.get("entity");
                if (menu != null) {
                    // 生成默认路由
                    routerDTO = this.produceDefaultRouter(menu);

                    // 是否为菜单内部跳转
                    if (this.isMultipleLevelMenu(menu, tree)) {
                        // 生成目录路由
                        routerDTO = this.produceDirectoryRouter(menu, this.buildRouterTree(tree.getChildren()));
                    }

                    // 是否为非外链并且是一级菜单
                    if (this.isSingleLevelMenu(menu)) {
                        // 生成菜单路由
                        routerDTO = this.produceMenuFrameRouter(menu);
                    }

                    // 是否为内链组件
                    if (menu.getParentId() == 0L && this.isInnerLink(menu)) {
                        // 生成内链路路由
                        routerDTO = this.produceInnerLinkRouter(menu);
                    }

                    routerList.add(routerDTO);
                }
            }
        }
        return routerList;
    }


    /**
     * 生成默认路由
     *
     * @param menu
     * @return
     */
    private RouterDTO produceDefaultRouter(Menu menu) {
        RouterDTO router = new RouterDTO();
        String routerName = StrUtil.upperFirst(menu.getPath());
        // 是否为非外链并且是一级菜单
        if (this.isSingleLevelMenu(menu)) {
            routerName = StrUtil.EMPTY;
        }
        router.setName(routerName);

        String routerPath = menu.getPath();
        // 内链打开外网方式
        if (menu.getParentId().intValue() != 0 && this.isInnerLink(menu)) {
            routerPath = this.trimHttpPrefixForInnerLink(routerPath);
        }
        if (isSingleLevelDirectory(menu)) {
            // 是否为非外链并且是一级目录
            routerPath = "/" + menu.getPath();
        } else if (isSingleLevelMenu(menu)) {
            // 是否为非外链并且是一级菜单
            routerPath = "/";
        }
        router.setPath(routerPath);

        String component = MenuComponentEnum.LAYOUT.getDescription();
        // 是否为是一级目录并且非外链
        if (StrUtil.isNotEmpty(menu.getComponent()) && !this.isSingleLevelMenu(menu)) {
            component = menu.getComponent();
        } else if (this.isInnerLinkView(menu)) {
            component = MenuComponentEnum.INNER_LINK.getDescription();
        } else if (this.isParentView(menu)) {
            component = MenuComponentEnum.PARENT_VIEW.getDescription();
        }
        router.setComponent(component);

        router.setQuery(menu.getQuery());
        router.setHidden(!menu.getIsVisible());
        router.setMeta(new MetaDTO(menu.getMenuName(), menu.getIcon(), !menu.getIsCache(), menu.getPath()));
        return router;
    }

    /**
     * 生成目录路由
     *
     * @param menu
     * @param children
     * @return
     */
    private RouterDTO produceDirectoryRouter(Menu menu, List<RouterDTO> children) {
        RouterDTO router = this.produceDefaultRouter(menu);
        if (CollUtil.isNotEmpty(children) && MenuTypeEnum.DIRECTORY.getValue().equals(menu.getMenuType())) {
            router.setAlwaysShow(true);
            router.setRedirect("noRedirect");
            router.setChildren(children);
        }
        return router;
    }

    /**
     * 生成菜单路由
     *
     * @param menu
     * @return
     */
    public RouterDTO produceMenuFrameRouter(Menu menu) {
        RouterDTO router = new RouterDTO();
        router.setMeta(null);

        RouterDTO children = new RouterDTO();
        children.setPath(menu.getPath());
        children.setComponent(menu.getComponent());
        children.setName(StrUtil.upperFirst(menu.getPath()));
        children.setMeta(new MetaDTO(menu.getMenuName(), menu.getIcon(), !menu.getIsCache(), menu.getPath()));
        children.setQuery(menu.getQuery());

        List<RouterDTO> childrenList = new ArrayList<>();
        childrenList.add(children);
        router.setChildren(childrenList);

        return router;
    }

    /**
     * 生成内链路路由
     *
     * @param menu
     * @return
     */
    public RouterDTO produceInnerLinkRouter(Menu menu) {
        RouterDTO router = new RouterDTO();
        router.setMeta(new MetaDTO(menu.getMenuName(), menu.getIcon()));
        router.setPath("/");

        List<RouterDTO> childrenList = new ArrayList<>();
        RouterDTO children = new RouterDTO();
        String routerPath = trimHttpPrefixForInnerLink(menu.getPath());
        children.setPath(routerPath);
        children.setComponent(MenuComponentEnum.INNER_LINK.getDescription());
        children.setName(StrUtil.upperFirst(routerPath));
        children.setMeta(new MetaDTO(menu.getMenuName(), menu.getIcon(), menu.getPath()));
        childrenList.add(children);
        router.setChildren(childrenList);

        return router;
    }

    /**
     * 是否为非外链并且是一级目录
     *
     * @param menu
     * @return
     */
    public boolean isSingleLevelDirectory(Menu menu) {
        return menu.getParentId().intValue() == 0
                && MenuTypeEnum.DIRECTORY.getValue().equals(menu.getMenuType())
                && !menu.getIsExternal();
    }

    /**
     * 是否为非外链并且是一级菜单
     *
     * @param menu
     * @return
     */
    public boolean isSingleLevelMenu(Menu menu) {
        return menu.getParentId().intValue() == 0
                && MenuTypeEnum.MENU.getValue().equals(menu.getMenuType())
                && !menu.getIsExternal();
    }

    /**
     * 是否为内链组件
     *
     * @param menu
     * @return
     */
    public boolean isInnerLink(Menu menu) {
        return !menu.getIsExternal()
                && (HttpUtil.isHttp(menu.getPath()) || HttpUtil.isHttps(menu.getPath()));
    }

    /**
     * 内链域名特殊字符替换
     *
     * @param path
     * @return
     */
    public String trimHttpPrefixForInnerLink(String path) {
        if (HttpUtil.isHttp(path)) {
            return StrUtil.stripIgnoreCase(path, "http://", "");
        }
        if (HttpUtil.isHttps(path)) {
            return StrUtil.stripIgnoreCase(path, "https://", "");
        }
        return path;
    }

    /**
     * 是否为inner_link_view组件
     *
     * @param menu
     * @return
     */
    public boolean isInnerLinkView(Menu menu) {
        return StrUtil.isEmpty(menu.getComponent())
                && menu.getParentId().intValue() != 0
                && this.isInnerLink(menu);
    }

    /**
     * 是否为parent_view组件
     *
     * @param menu
     * @return
     */
    public boolean isParentView(Menu menu) {
        return StrUtil.isEmpty(menu.getComponent())
                && menu.getParentId().intValue() != 0
                && MenuTypeEnum.DIRECTORY.getValue().equals(menu.getMenuType());
    }

    /**
     * 是否为菜单内部跳转
     *
     * @param menu
     * @param tree
     * @return
     */
    public boolean isMultipleLevelMenu(Menu menu, Tree<Long> tree) {
        return MenuTypeEnum.DIRECTORY.getValue().equals(menu.getMenuType())
                && tree.hasChild();
    }
}
